package com.ruoyi.project.emmanuel.account.service.impl;

import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.ToolUtils;
import com.ruoyi.common.utils.security.ShiroUtils;
import com.ruoyi.common.utils.text.Convert;
import com.ruoyi.framework.datasource.DataSourceUtils;
import com.ruoyi.project.emmanuel.account.domain.AccountAccount;
import com.ruoyi.project.emmanuel.account.domain.AccountClass;
import com.ruoyi.project.emmanuel.account.domain.AccountMoney;
import com.ruoyi.project.emmanuel.account.domain.UserAccount;
import com.ruoyi.project.emmanuel.account.mapper.AccountAccountMapper;
import com.ruoyi.project.emmanuel.account.mapper.AccountBillMapper;
import com.ruoyi.project.emmanuel.account.mapper.AccountClassMapper;
import com.ruoyi.project.emmanuel.account.service.IAccountAccountService;
import com.ruoyi.project.emmanuel.account.service.IAccountClassService;
import com.ruoyi.project.emmanuel.account.service.IAccountMoneyService;
import com.ruoyi.project.system.dict.domain.DictData;
import com.ruoyi.project.system.dict.service.IDictTypeService;
import com.ruoyi.project.system.user.domain.User;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.ModelMap;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.util.*;
import java.util.stream.Collectors;
import static com.ruoyi.common.utils.DateUtils.YYYY_MM_DD;

/**
 * 记账账户Service业务层处理
 *
 * @author ruoyi
 * @date 2022-01-21
 */
@Service
public class AccountAccountServiceImpl implements IAccountAccountService {
    @Autowired
    private AccountAccountMapper accountAccountMapper;
    @Autowired
    private IAccountClassService accountClassService;
    @Autowired
    private AccountBillMapper accountBillMapper;
    @Autowired
    private AccountClassMapper accountClassMapper;
    @Autowired
    private IAccountMoneyService accountMoneyService;
    @Autowired
    private IDictTypeService dictTypeService;

    /**
     * 查询记账账户
     *
     * @param id 记账账户主键
     * @return 记账账户
     */
    @Override
    public AccountAccount selectAccountAccountById(Long id) {
        return accountAccountMapper.selectAccountAccountById(id);
    }

    /**
     * 查询记账账户列表
     *
     * @param accountAccount 记账账户
     * @return 记账账户
     */
    @Override
    public List<AccountAccount> selectAccountAccountList(AccountAccount accountAccount) {
        accountAccount.setUserId(ShiroUtils.getUserId());
        return accountAccountMapper.selectAccountAccountList(accountAccount);
    }

    /**
     * 新增记账账户
     *
     * @param accountAccount 记账账户
     * @return 结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int insertAccountAccount(AccountAccount accountAccount) {
        this.isExist(accountAccount);
        // 添加账户
        accountAccount.setCreateBy(ShiroUtils.getLoginName());
        accountAccountMapper.insertAccountAccount(accountAccount);
        // 添加账户-用户管理
        UserAccount ua = new UserAccount();
        ua.setAccountId(accountAccount.getId());
        ua.setUserId(ShiroUtils.getUserId());
        ua.setAdministrators("1");
        return accountAccountMapper.insertUserAccount(ua);
    }

    /**
     * 判断是否已经存在
     *
     * @param accountAccount
     */
    private void isExist(AccountAccount accountAccount) {
        // 这里只返回了是否隐藏
        String accountName = accountAccount.getAccountName();
        accountAccount = accountAccountMapper.isExist(accountAccount);
        if (ToolUtils.isEmpty(accountAccount)) {
            return;
        }
        // 1为隐藏
        if (Objects.equals("1", accountAccount.getVisible())) {
            throw new RuntimeException(accountName + "已存在,且为隐藏状态");
        }
        throw new RuntimeException(accountName + "已存在,换个名字试试");

    }

    /**
     * 修改记账账户
     *
     * @param accountAccount 记账账户
     * @return 结果
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public int updateAccountAccount(AccountAccount accountAccount) {
        this.isExist(accountAccount);
        accountAccount.setUpdateBy(ShiroUtils.getLoginName());
        return accountAccountMapper.updateAccountAccount(accountAccount);
    }

    /**
     * 批量删除记账账户
     *
     * @param ids 需要删除的记账账户主键
     * @return 结果
     */
    @Override
    public int deleteAccountAccountByIds(String ids) {
        return accountAccountMapper.deleteAccountAccountByIds(Convert.toStrArray(ids));
    }

    /**
     * 删除记账账户信息
     *
     * @param id 记账账户主键
     * @return 结果
     */
    @Override
    public int deleteAccountAccountById(Long id) {
        return accountAccountMapper.deleteAccountAccountById(id);
    }

    @Override
    public AccountAccount selectAccountNameById(Long id) {
        return accountAccountMapper.selectAccountNameById(id,ShiroUtils.getUserId());
    }

    /**
     * 账单分析
     *
     * @param accountId 账单ID
     * @param modelMap
     */
    @Override
    public void accountAnalysisPage(Long accountId, ModelMap modelMap) {

        Long userId = ShiroUtils.getUserId();

        // 获取授权账户
        AccountAccount accountAccount = accountAccountMapper.selectAccountNameById(accountId, userId);

        if (ToolUtils.isEmpty(accountAccount)) {
            return;
        }

        // 按分类账单分析
        List<Map<String, Object>> accountClassList = accountAccountMapper.accountCountByClass(accountId,userId);
        // 排序
        accountClassList = accountClassList.stream().sorted(Comparator.comparing((Map<String, Object> e) -> (String) e.get("classType")).thenComparing(e -> (String) e.get("status")).thenComparing(e -> (Integer) e.get("orderNum"))).collect(Collectors.toList());
        // 支出获取账单排行前30
        List<Map<String, Object>> payAccountAnalysisByMonthLastList = accountBillMapper.accountAnalysisByMonthLast(accountId, userId, "2");

        // 总收入/总支出/总不计入
        getBillTotal(modelMap, accountClassList);

        modelMap.put("accountId", accountId);
        modelMap.put("accountName", accountAccount.getAccountName());
        modelMap.put("accountClassList", accountClassList);
        modelMap.put("payAccountAnalysisByMonthLastList", payAccountAnalysisByMonthLastList);

    }

    /**
     * 根据账单状态 status 统计 总收入/总支出/总不计入
     *
     * @param modelMap         视图模型
     * @param accountClassList 账单详情
     */
    @Override
    public void getBillTotal(ModelMap modelMap, List<Map<String, Object>> accountClassList) {
        // 获取数据库类型
        String dbType = DataSourceUtils.getDbType();

        // 总收入/总支出/总不计入
        BigDecimal totalIncome = accountClassList.stream().filter(e -> Objects.equals("1", e.get("status"))).map(e -> StringUtils.equalsAnyIgnoreCase(DataSourceUtils.SQLITE, dbType) ? BigDecimal.valueOf((Double) e.get("money")) : (BigDecimal) (e.get("money"))).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalPay = accountClassList.stream().filter(e -> Objects.equals("2", e.get("status"))).map(e -> StringUtils.equalsAnyIgnoreCase(DataSourceUtils.SQLITE, dbType) ? BigDecimal.valueOf((Double) e.get("money")) : (BigDecimal) (e.get("money"))).reduce(BigDecimal.ZERO, BigDecimal::add);
        BigDecimal totalIgnore = accountClassList.stream().filter(e -> Objects.equals("3", e.get("status"))).map(e -> StringUtils.equalsAnyIgnoreCase(DataSourceUtils.SQLITE, dbType) ? BigDecimal.valueOf((Double) e.get("money")) : (BigDecimal) (e.get("money"))).reduce(BigDecimal.ZERO, BigDecimal::add);
        modelMap.put("totalPay", totalPay);
        modelMap.put("totalIncome", totalIncome);
        modelMap.put("totalIgnore", totalIgnore);
    }

    /**
     * 查询已分配用户账户列表
     *
     * @param user 用户信息
     * @return 用户信息集合信息
     */
    @Override
    public List<User> selectAllocatedList(User user) {
        return accountAccountMapper.selectAllocatedList(user);
    }

    /**
     * 取消授权用户账户
     *
     * @param userAccount 用户和账户关联信息
     * @return 结果
     */
    @Override
    public int deleteAuthUser(UserAccount userAccount) {
        administrators(ShiroUtils.getUserId(), userAccount.getAccountId());
        return accountAccountMapper.deleteUserRoleInfo(userAccount);
    }

    /**
     * 批量取消授权用户账户
     *
     * @param accountId 账户ID
     * @param userIds   需要删除的用户数据ID
     * @return 结果
     */
    @Override
    public int deleteAuthUsers(Long accountId, String userIds) {
        administrators(ShiroUtils.getUserId(), accountId);
        return accountAccountMapper.deleteUserRoleInfos(accountId, Convert.toLongArray(userIds));
    }

    /**
     * 根据条件分页查询未分配用户账户列表
     *
     * @param user 用户信息
     * @return 用户信息集合信息
     */
    @Override
    public List<User> selectUnallocatedList(User user) {
        return accountAccountMapper.selectUnallocatedList(user);
    }

    /**
     * 批量选择授权用户账户
     *
     * @param accountId 账户ID
     * @param userIds   需要删除的用户数据ID
     * @return 结果
     */
    @Override
    public int insertAuthUsers(Long accountId, String userIds) {
        administrators(ShiroUtils.getUserId(), accountId);
        Long[] users = Convert.toLongArray(userIds);
        // 新增用户与账户管理
        List<UserAccount> list = new ArrayList<UserAccount>();
        for (Long userId : users) {
            UserAccount ua = new UserAccount();
            ua.setUserId(userId);
            ua.setAccountId(accountId);
            list.add(ua);
        }
        return accountAccountMapper.batchUserAccount(list);
    }

    /**
     * 校验用户账户管理员
     *
     * @param userId    用户ID
     * @param accountId 账户ID
     */
    private void administrators(Long userId, Long accountId) {
        UserAccount userAccount = accountAccountMapper.selectUserAccount(userId, accountId);
        if (!Objects.equals("1", userAccount.getAdministrators())) {
            throw new RuntimeException("非账户管理员，禁止修改");
        }
    }

    /**
     * 获取用户账户管理
     *
     * @param userId    用户id
     * @param accountId 账户ID
     * @return
     */
    @Override
    public UserAccount selectUserAccount(Long userId, Long accountId) {
        return accountAccountMapper.selectUserAccount(userId, accountId);
    }

    /**
     * 导出记账账户-行账单导出
     *
     * @param response
     * @param accountAccount
     * @throws IOException
     */
    @Override
    public void exportAccountExcel(HttpServletResponse response, AccountAccount accountAccount) throws Exception {

        // 获取账单
        accountAccount = accountAccountMapper.selectAccountAccountById(accountAccount.getId());

        // 这里注意 有同学反应使用swagger 会导致各种问题，请直接用浏览器或者用postman
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode(accountAccount.getAccountName()+"_"+DateUtils.dateTimeNow(), "UTF-8").replaceAll("\\+", "%20");
        response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");

        // 数据字典
        List<DictData> dictDataList = dictTypeService.selectDictDataByType("account_income_pay");

        // 获取分类数据
        AccountClass accountClass = new AccountClass();
        Map<String, Object> params = new HashMap<>();
        params.put("accountId", accountAccount.getId());
        accountClass.setParams(params);
        List<AccountClass> accountClassesList = accountClassMapper.selectAccountClassListByAccountId(accountClass);

        // 获取一级表头/二级表头/三级表头/数据
        Map<String, Object> titleMap = accountClassService.selectMoneyByClass(accountAccount, accountClassesList);

        // 获取原始数据
        Object titleOneObj = titleMap.get("mapTitleOneList"); // 一级表头
        Object titleTwoObj = titleMap.get("mapTitleTwoList"); // 二级表头
        Object titleObj = titleMap.get("mapTitleList");  // 三级表头
        Object dataObj = titleMap.get("mapDataList");    // 数据

        // 类型转换
        List<Map<String, Object>> mapTitleOneList = titleOneObj instanceof List ? (List<Map<String, Object>>) titleOneObj : null; // 一级表头
        List<Map<String, Object>> mapTitleTwoList = titleOneObj instanceof List ? (List<Map<String, Object>>) titleTwoObj : null; // 一级表头
        List<Map<String, Object>> mapTitleList = titleObj instanceof List ? (List<Map<String, Object>>) titleObj : null; // 二级表头
        List<Map<String, Object>> mapDataList = dataObj instanceof List ? (List<Map<String, Object>>) dataObj : null; // 数据

        // 创建一个ExcelWriter对象
        ExcelWriter excelWriter = EasyExcel.write(response.getOutputStream()).build();
        // 第一个sheet 每次都要创建writeSheet 这里注意必须指定sheetNo ,sheetName必须不一样
        WriteSheet writeSheet = EasyExcel.writerSheet(1, "分类月账单(" + accountAccount.getAccountName()+")")
                .head(getYearHeadByMonth(mapTitleOneList, mapTitleTwoList, mapTitleList,dictDataList)) // 表头
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 自动列宽
                .build();
        // 写入数据
        excelWriter.write(getYearDataByMonth(mapTitleList, mapDataList), writeSheet);

        AccountMoney accountMoney = new AccountMoney();
        accountMoney.setAccountId(accountAccount.getId());
        accountMoney.setStatus("0");
        List<AccountMoney> accountMoneyList = accountMoneyService.selectAccountMoneyList(accountMoney, null);

        // 第二个sheet
        WriteSheet writeSheet2 = EasyExcel.writerSheet(2, "账单详情(" + accountAccount.getAccountName()+")")
                .head(getMonthHead())  // 表头
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())  // 自动列宽
                .build();
        excelWriter.write(getMonthData(accountMoneyList,dictDataList), writeSheet2);  // 写入数据

        excelWriter.finish();  // 关闭流

    }

    /**
     * sheet1 组织表头
     *
     * @param mapTitleOneList 一级表头
     * @param mapTitleTwoList 二级表头
     * @param mapTitleList    三级表头
     * @return
     */
    private List<List<String>> getYearHeadByMonth(List<Map<String, Object>> mapTitleOneList, List<Map<String, Object>> mapTitleTwoList, List<Map<String, Object>> mapTitleList,List<DictData> dictDataList) {

        // 用于存放组织好的表头
        List<List<String>> headResult = new ArrayList<>();

        // 使用 Map 存储每个 code 对应的表头
        Map<String, String> titleOneMap = new HashMap<>();
        Map<String, List<Map<String, Object>>> titleTwoMap = new HashMap<>();
        Map<String, List<Map<String, Object>>> titleMap = new HashMap<>();

        // 将一级表头存入 titleOneMap
        for (Map<String, Object> titleOne : mapTitleOneList) {
            String code = String.valueOf(titleOne.get("code"));
            titleOneMap.put(code, String.valueOf(titleOne.get("title")));
        }

        // 将二级表头存入 titleTwoMap，按 parentCode 分类
        for (Map<String, Object> titleTwo : mapTitleTwoList) {
            String parentCode = String.valueOf(titleTwo.get("parentCode"));
            titleTwoMap.computeIfAbsent(parentCode, k -> new ArrayList<>()).add(titleTwo);
        }

        // 将三级表头存入 titleMap，按 parentCode 分类
        for (Map<String, Object> title : mapTitleList) {
            String parentCode = String.valueOf(title.get("parentCode"));
            titleMap.computeIfAbsent(parentCode, k -> new ArrayList<>()).add(title);
        }

        // 排序一级表头
        List<String> sortedTitleOneCodes = new ArrayList<>(titleOneMap.keySet());
        sortedTitleOneCodes.sort(Comparator.comparingInt(code -> {
            // 将 null 的 parentCode 视为最小值
            return code.equals("-1") ? Integer.MIN_VALUE : Integer.parseInt(code);
        }));

        // 遍历一级表头并拼接二级和三级表头
        for (String code : sortedTitleOneCodes) {
            String titleOne = titleOneMap.get(code);

            // 获取对应的二级表头列表
            List<Map<String, Object>> titleTwoListForCode = titleTwoMap.get(code);
            if (titleTwoListForCode != null) {
                // 排序二级表头
                titleTwoListForCode.sort(Comparator.comparingInt(titleTwo -> {
                    String titleTwoCode = String.valueOf(titleTwo.get("code"));
                    return titleTwoCode.equals("-1") ? Integer.MIN_VALUE : Integer.parseInt(titleTwoCode);
                }));

                // 对每个二级表头进行处理
                for (Map<String, Object> titleTwo : titleTwoListForCode) {
                    String titleTwoText = String.valueOf(titleTwo.get("title"));
                    String titleTwoCode = String.valueOf(titleTwo.get("code"));

                    // 获取对应的三级表头列表
                    List<Map<String, Object>> titleListForCode = titleMap.get(titleTwoCode);
                    if (titleListForCode != null) {

                        // 对每个三级表头进行处理
                        for (Map<String, Object> title : titleListForCode) {
                            String titleText = String.valueOf(title.get("title"));

                            // 二级单元格值转换
                            for (DictData dictData : dictDataList) {
                                if (StringUtils.equals(dictData.getDictValue(), titleTwoText)) {
                                    titleTwoText = dictData.getDictLabel();
                                    break;
                                }
                            }
                            headResult.add(Arrays.asList(titleOne, titleTwoText, titleText));
                        }
                    }
                }
            }
        }

        return headResult;
    }

    /**
     * sheet1 组织数据
     *
     * @param mapTitleList 三级表头
     * @param mapDataList  值
     * @return
     */
    private List<List<Object>> getYearDataByMonth(List<Map<String, Object>> mapTitleList, List<Map<String, Object>> mapDataList) {

        List<List<Object>> dataList = new ArrayList<>();

        // 组合数据
        for (Map<String, Object> dataMap : mapDataList) {
            List<Object> list = new ArrayList<>();
            for (Map<String, Object> titleMap : mapTitleList) {
                list.add(dataMap.get(String.valueOf(titleMap.get("field"))));
            }
            dataList.add(list);
        }

        return dataList;
    }

    /**
     * sheet2 组织表头
     *
     * @return
     */
    private List<List<String>> getMonthHead() {
        return Arrays.asList(
                Arrays.asList("类型"),
                Arrays.asList("分类"),
                Arrays.asList("金额"),
                Arrays.asList("消费日期"),
                Arrays.asList("是否计入总账"),
                Arrays.asList("备注")
        );
    }

    /**
     * sheet2 组织数据
     *
     * @param accountMoneyList 金额列表
     * @return
     */
    private List<List<Object>> getMonthData(List<AccountMoney> accountMoneyList,List<DictData> dictDataList) {
        List<List<Object>> dataList = new ArrayList<>();
        for (AccountMoney accountMoney : accountMoneyList) {
//            // 状态 （收入/支出/不计入）
//            String status = accountMoney.getStatus();
//            String statusName = "";
//            if (Objects.equals("1",status)){
//                statusName ="收入";
//            }else if (Objects.equals("2",status)){
//                statusName ="支出";
//            }else if (Objects.equals("3",status)){
//                statusName = "不计入";
//            }else {
//                statusName = "未知";
//            }
            // 类型转换(数据字典)
            String classTypeName = "";
            for (DictData dictData : dictDataList) {
                if (StringUtils.equals(dictData.getDictValue(), accountMoney.getClassType())) {
                    classTypeName = dictData.getDictLabel();
                    break;
                }
            }
            // 是否记入总账, 0=是 1=否
            String isTotal = Objects.equals(0, accountMoney.getIsTotal()) ? "是" : "否";

            dataList.add(
                    Arrays.asList(
                            classTypeName,
                            accountMoney.getClassName(),
                            accountMoney.getMoney(),
                            DateUtils.parseDateToStr(YYYY_MM_DD,accountMoney.getMoneyDate()),
                            isTotal,
                            accountMoney.getMoneyIntroduce()
                    )
            );
        }
        return dataList;
    }

}
